// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

// package collider 定义了基于 WebSocket 的信令服务。
package collider

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"html"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/websocket"
)

const registerTimeoutSec = 3600
const wsReadTimeoutSec = 60 * 60 * 24

// Collider 是信令服务器的主结构。
type Collider struct {
	*roomTable
	dash *dashboard
}

// NewCollider 创建一个新的 Collider 实例。
func NewCollider(rs string) *Collider {
	return &Collider{
		roomTable: newRoomTable(time.Second*registerTimeoutSec, rs),
		dash:      newDashboard(),
	}
}

// Run 启动 collider 服务器并阻塞线程，直到程序退出。
func (c *Collider) Run(p int, useTls bool) {
	log.Printf(">>> Collider 开始运行: 端口=%d, TLS=%t", p, useTls)

	// (关键修复) 创建一个自定义的 WebSocket 服务器，并将其 Handler 设置为 nil，
	// 这会有效地禁用默认的“来源检查 (Origin Check)”，从而修复 403/500 错误。
	wsServer := websocket.Server{
		Handler: websocket.Handler(c.wsHandler),
	}
	http.Handle("/ws", wsServer)

	http.HandleFunc("/status", c.httpStatusHandler)
	http.HandleFunc("/", c.httpHandler)

	var e error
	pstr := ":" + strconv.Itoa(p)
	if useTls {
		config := &tls.Config{
			CipherSuites: []uint16{
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
			},
			PreferServerCipherSuites: true,
		}
		server := &http.Server{Addr: pstr, Handler: nil, TLSConfig: config}
		log.Printf(">>> 正在监听 TLS on %s", pstr)
		e = server.ListenAndServeTLS("/cert/cert.pem", "/cert/key.pem")
	} else {
		log.Printf(">>> 正在监听 on %s (已禁用来源检查)", pstr)
		e = http.ListenAndServe(pstr, nil)
	}

	if e != nil {
		log.Fatal("!!! 运行失败: " + e.Error())
	}
}

// httpStatusHandler 是一个处理获取 collider 状态的 GET 请求的 HTTP 处理器。
func (c *Collider) httpStatusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "GET")
	rp := c.dash.getReport(c.roomTable)
	enc := json.NewEncoder(w)
	if err := enc.Encode(rp); err != nil {
		err = errors.New("编码到 JSON 失败: err=" + err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		c.dash.onHttpErr(err)
	}
}

// httpHandler 是一个处理 GET/POST/DELETE 请求的 HTTP 处理器。
func (c *Collider) httpHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "POST, DELETE")
	p := strings.Split(r.URL.Path, "/")
	if len(p) != 3 {
		c.httpError("无效的路径: "+html.EscapeString(r.URL.Path), w)
		return
	}
	rid, cid := p[1], p[2]
	log.Printf(">>> HTTP %s: 房间ID=%s, 客户端ID=%s", r.Method, rid, cid)
	switch r.Method {
	case "POST":
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			c.httpError("读取请求体失败: "+err.Error(), w)
			return
		}
		m := string(body)
		if m == "" {
			c.httpError("空的请求体", w)
			return
		}
		if err := c.roomTable.send(rid, cid, m); err != nil {
			c.httpError("发送消息失败: "+err.Error(), w)
			return
		}
	case "DELETE":
		c.roomTable.remove(rid, cid)
	default:
		return
	}
	io.WriteString(w, "OK\n")
}

// wsHandler 是一个 WebSocket 服务器，处理来自 WebSocket 客户端的请求。
func (c *Collider) wsHandler(ws *websocket.Conn) {
	var rid, cid string
	registered := false
	var msg wsClientMsg
	log.Printf(">>> 新的 WebSocket 连接已建立: %s", ws.Request().RemoteAddr)
loop:
	for {
		err := ws.SetReadDeadline(time.Now().Add(time.Duration(wsReadTimeoutSec) * time.Second))
		if err != nil {
			c.wsError("ws.SetReadDeadline 错误: "+err.Error(), ws)
			break
		}
		err = websocket.JSON.Receive(ws, &msg)
		if err != nil {
			if err != io.EOF {
				c.wsError("websocket.JSON.Receive 错误: "+err.Error(), ws)
			} else {
				log.Printf(">>> WebSocket 连接已由客户端关闭 (EOF): %s", ws.Request().RemoteAddr)
			}
			break
		}
		log.Printf(">>> 收到 WebSocket 消息: cmd=%s, 房间ID=%s, 客户端ID=%s", msg.Cmd, msg.RoomID, msg.ClientID)
		switch msg.Cmd {
		case "register":
			if registered {
				c.wsError("重复的注册请求", ws)
				break loop
			}
			if msg.RoomID == "" || msg.ClientID == "" {
				c.wsError("无效的注册请求: 缺少 'clientid' 或 'roomid'", ws)
				break loop
			}
			if err = c.roomTable.register(msg.RoomID, msg.ClientID, ws); err != nil {
				c.wsError(err.Error(), ws)
				break loop
			}
			registered, rid, cid = true, msg.RoomID, msg.ClientID
			c.dash.incrWs()
			log.Printf(">>> 客户端 %s 在房间 %s 中注册成功", cid, rid)
			defer c.roomTable.deregister(rid, cid)
			break
		case "send":
			if !registered {
				c.wsError("客户端未注册", ws)
				break loop
			}
			if msg.Msg == "" {
				c.wsError("无效的发送请求: 缺少 'msg'", ws)
				break loop
			}
			c.roomTable.send(rid, cid, msg.Msg)
			break
		default:
			c.wsError("无效的消息: 意外的 'cmd'", ws)
			break
		}
	}
	log.Printf(">>> 关闭 WebSocket 连接: 房间ID=%s, 客户端ID=%s", rid, cid)
	ws.Close()
}

// httpError 记录一个 HTTP 错误并将其发送给客户端。
func (c *Collider) httpError(msg string, w http.ResponseWriter) {
	err := errors.New(msg)
	http.Error(w, err.Error(), http.StatusInternalServerError)
	c.dash.onHttpErr(err)
}

// wsError 记录一个 WebSocket 错误并将其发送给客户端。
func (c *Collider) wsError(msg string, ws *websocket.Conn) {
	err := errors.New(msg)
	sendServerErr(ws, msg)
	c.dash.onWsErr(err)
}
